package zzps.exeed.kernel.aop.asm;

import org.objectweb.asm.*;
import zzps.exeed.kernel.holder.MethodHolder;
import zzps.exeed.kernel.holder.PandaHolder;

import java.io.FileOutputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;

/**
 * @author noear
 * @since 2.2
 */
public class AsmClassCreator {
    // 动态生成代理类的后缀
    public static final String PROXY_CLASSNAME_SUFFIX ="$$ExeedAsmProxy";
    public static final int ASM_JDK_VERSION = Opcodes.V17;
    private static final String FIELD_INVOCATIONHANDLER = "invocationHandler";
    public static final String set_invocationhandler_method = "setInvocationHandler";
    public static final String set_overwrite_method = "setOverwriteMethod";
    private static final String METHOD_INVOKE = "invokeInvocationHandler";
    private static final String METHOD_INVOKE_DESC = "(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;";

    public static Class<?> createClass(Class<?> targetClass, AsmClassLoader classLoader, PandaHolder<?> pandaHolder) throws Exception {
        // 获取目标类的一些数据
        ClassReader reader = new ClassReader(targetClass.getName());//某些情况下直接通过类名可能会获取不到数据
        TargetClassVisitor targetClassVisitor = new TargetClassVisitor(pandaHolder);
        reader.accept(targetClassVisitor, ClassReader.SKIP_DEBUG);
        // 判断是否是FINAL的
        if (targetClassVisitor.isFinaled()) {
            throw new IllegalArgumentException("class is final");
        }
        // 开始生成代理类
        ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
        String newClassName = generateProxyClassName(targetClass);
        String newClassInnerName = newClassName.replace(".", "/");
        String targetClassName = targetClass.getName();
        String targetClassInnerName = Type.getInternalName(targetClass);
        // 创建类
        newClass(writer, newClassInnerName, targetClassInnerName);
        // 添加 InvocationHandler 字段
        addField(writer);
        // 添加 InvocationHandler 的setter
        addSetterMethod(writer, newClassInnerName);
        //panda holder
        setOverwriteMethod(writer, newClassInnerName,pandaHolder);
        // 添加构造器，直接调用 super
        addConstructor(writer, targetClassInnerName);
        // 添加调用 InvocationHandler 的方法
        addInvokeMethod(writer, newClassInnerName);
        addMethod(writer,newClassInnerName,pandaHolder);

        // 生成二进制数据
        byte[] bytes = writer.toByteArray();

        String path = classLoader.getResource("").getPath();
        FileOutputStream fileOutputStream = new FileOutputStream(path+newClassInnerName+".class");
        fileOutputStream.write(bytes);
        fileOutputStream.close();

        // 从指定ClassLoader加载Class
        Class<?> proxyClass = classLoader.transfer2Class(bytes);

        return proxyClass;
    }

    /**
     * 生成代理类的类名生成规则
     */
    private static String generateProxyClassName(Class<?> targetClass) {
        return targetClass.getPackage().getName() + "." + targetClass.getSimpleName() + PROXY_CLASSNAME_SUFFIX;
    }

    /**
     * 创建类
     */
    private static void newClass(ClassWriter writer, String newClassName, String targetClassName) throws Exception {
        int access = Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL;
        writer.visit(ASM_JDK_VERSION, access, newClassName, null, targetClassName, null);
    }

    /**
     * 添加 invocationHandler 字段
     */
    private static void addField(ClassWriter writer) throws Exception {
        FieldVisitor fieldVisitor = writer.visitField(Opcodes.ACC_PRIVATE, FIELD_INVOCATIONHANDLER,
                Type.getDescriptor(InvocationHandler.class), null, null);
        fieldVisitor.visitEnd();
    }

    /**
     * 添加 invocationHandler 的 setter 方法
     */
    private static void addSetterMethod(ClassWriter writer, String owner) throws Exception {
        String methodDesc = "(" + Type.getDescriptor(InvocationHandler.class) + ")V";
        MethodVisitor methodVisitor = writer.visitMethod(Opcodes.ACC_PUBLIC, set_invocationhandler_method, methodDesc, null, null);
        methodVisitor.visitCode();
        methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
        methodVisitor.visitVarInsn(Opcodes.ALOAD, 1);
        methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, owner, FIELD_INVOCATIONHANDLER, Type.getDescriptor(InvocationHandler.class));
        methodVisitor.visitInsn(Opcodes.RETURN);
        methodVisitor.visitMaxs(0, 0);
        methodVisitor.visitEnd();
    }


    /**
     * 添加构造器
     */
    private static void addConstructor(ClassWriter writer, String targetClassInnerName) throws Exception {
        MethodVisitor methodVisitor = writer.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
        methodVisitor.visitCode();
        methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
        methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, targetClassInnerName, "<init>", "()V", false);
        methodVisitor.visitInsn(Opcodes.RETURN);
        methodVisitor.visitMaxs(0,0);
        methodVisitor.visitEnd();
    }
    /**
     * 添加调用 invocationHandler 的 invoke 方法
     */
    private static void addInvokeMethod(ClassWriter writer, String owner) throws Exception {

        Label startLabel = new Label();
        Label endLabel = new Label();
        Label handlerLabel = new Label();
        MethodVisitor methodVisitor = writer.visitMethod(Opcodes.ACC_PRIVATE | Opcodes.ACC_VARARGS,
                METHOD_INVOKE, METHOD_INVOKE_DESC, null, null);
        methodVisitor.visitCode();
        methodVisitor.visitTryCatchBlock(startLabel,endLabel,handlerLabel,Type.getInternalName(Throwable.class));
        methodVisitor.visitLabel(startLabel);
        //取字段
        methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
        methodVisitor.visitFieldInsn(Opcodes.GETFIELD, owner, FIELD_INVOCATIONHANDLER, Type.getDescriptor(InvocationHandler.class));
        //取参数
        methodVisitor.visitVarInsn(Opcodes.ALOAD, 1);
        methodVisitor.visitVarInsn(Opcodes.ALOAD, 2);
        methodVisitor.visitVarInsn(Opcodes.ALOAD, 3);
        String handlerName = Type.getInternalName(InvocationHandler.class);
        String handlerMethodName = "invoke";
        String handlerDesc = "(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;";
        // 调用 invocationHandler.invoke 方法
        methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, handlerName, handlerMethodName, handlerDesc, true);
        methodVisitor.visitInsn(Opcodes.ARETURN);

        methodVisitor.visitLabel(endLabel);
        methodVisitor.visitLabel(handlerLabel);
        methodVisitor.visitVarInsn(Opcodes.ASTORE, 0);
        methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
        methodVisitor.visitTypeInsn(Opcodes.NEW,Type.getInternalName(RuntimeException.class));
        methodVisitor.visitInsn(Opcodes.DUP);
        methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
        methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(RuntimeException.class), "<init>", "(Ljava/lang/Throwable;)V", false);
        methodVisitor.visitInsn(Opcodes.ATHROW);
        methodVisitor.visitMaxs(0,0);
        methodVisitor.visitEnd();
    }

    private static void addMethod(ClassWriter writer,String owner,PandaHolder<?> pandaHolder){
        Map<Method, MethodHolder> methodedHolderMap = pandaHolder.methodHolderMap();
        for (Map.Entry<Method, MethodHolder> entry : methodedHolderMap.entrySet()) {
            Method method = entry.getKey();
            String methodName = method.getName();
            String methodDescriptor = Type.getMethodDescriptor(method);

            //添加方法字段
            FieldVisitor fieldVisitor = writer.visitField(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC, methodName, Type.getDescriptor(Method.class), null, null);
            fieldVisitor.visitEnd();
            //添加方法
            MethodVisitor methodVisitor = writer.visitMethod(Opcodes.ACC_PUBLIC, methodName, methodDescriptor, null, null);
            methodVisitor.visitCode();
            methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
            methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
            //获取上面添加的方法字段
            methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, owner, methodName, Type.getDescriptor(Method.class));
            Type[] argumentTypes = Type.getArgumentTypes(methodDescriptor);
            // 实例化数组，容量对应方法的参数个数
            methodVisitor.visitIntInsn(Opcodes.BIPUSH, argumentTypes.length);
            methodVisitor.visitTypeInsn(Opcodes.ANEWARRAY, Type.getInternalName(Object.class));
            // 局布变量表入栈，基本类型需要装箱
            int index = 1;
            for (int i = 0; i < argumentTypes.length; i++) {
                Type type = argumentTypes[i];
                methodVisitor.visitInsn(Opcodes.DUP);
                // 放入数组的下标位置
                methodVisitor.visitIntInsn(Opcodes.BIPUSH, i);
                // 局部变量表的索引
                PrimitiveTypeHelper helper = PrimitiveTypeHelper.getHelper(type);
                if (helper == null){
                    methodVisitor.visitVarInsn(Opcodes.ALOAD, index);
                }else {
                    methodVisitor.visitVarInsn(helper.opcode, index);
                    //基本类型需要装箱
                    methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, helper.owner, "valueOf", helper.packDescriptor, false);
                }
                methodVisitor.visitInsn(Opcodes.AASTORE);
                if (type.equals(Type.DOUBLE_TYPE)||type.equals(Type.LONG_TYPE)){
                    index += 2;
                }else {
                    index += 1;
                }
            }
            // 调用 invokeInvocationHandler 方法
            methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, owner, METHOD_INVOKE, METHOD_INVOKE_DESC, false);
            Type returnType = Type.getReturnType(method);
            PrimitiveTypeHelper helper = PrimitiveTypeHelper.getHelper(returnType);
            if (helper == null){
                if (returnType.equals(Type.VOID_TYPE)){
                    methodVisitor.visitInsn(Opcodes.RETURN);
                }else {
                    methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, returnType.getInternalName());
                    methodVisitor.visitInsn(Opcodes.ARETURN);
                }
            }else {
                //基本类型需要拆箱
                methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, helper.owner);
                methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, helper.owner,helper.unpackMethodName, helper.unpackDescriptor, false);
                methodVisitor.visitInsn(helper.returnCode);
            }
            methodVisitor.visitMaxs(0, 0);
            methodVisitor.visitEnd();
        }
    }

    private static void setOverwriteMethod(ClassWriter writer, String owner, PandaHolder<?> pandaHolder) throws Exception {
        String methodDesc = "(" + Type.getDescriptor(PandaHolder.class) + ")V";
        MethodVisitor methodVisitor = writer.visitMethod(Opcodes.ACC_PUBLIC, set_overwrite_method, methodDesc, null, null);
        methodVisitor.visitCode();
        Map<Method, MethodHolder> methodHolderMap = pandaHolder.methodHolderMap();
        for (Map.Entry<Method, MethodHolder> entry : methodHolderMap.entrySet()) {
            Method method = entry.getKey();
            String name = method.getName();
            methodVisitor.visitVarInsn(Opcodes.ALOAD,1);
            methodVisitor.visitLdcInsn(name);
            methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL,Type.getInternalName(PandaHolder.class),"getMethod","(Ljava/lang/String;)Ljava/lang/reflect/Method;");
            methodVisitor.visitFieldInsn(Opcodes.PUTSTATIC,owner,name,Type.getDescriptor(Method.class));
        }
        methodVisitor.visitInsn(Opcodes.RETURN);
        methodVisitor.visitMaxs(0, 0);
        methodVisitor.visitEnd();
    }




}
